package flinty.autocomplete.combobox;

import java.util.List;
import java.util.logging.Logger;

import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;

/**
 * <code>BaseAutoCompleteComboBoxModel</code> is an abstract class that defines
 * autocompletion logic. Abstract methods of the class gives a programmer a
 * flexibility by writing a successors which are appropriate for different
 * usecases
 */
public abstract class BaseAutoCompleteComboBoxModel<T> extends
		AbstractListModel implements ComboBoxModel {
	private static final long serialVersionUID = 3666619335614672262L;

	private static Logger logger = AutoCompleteComboBoxDecorator
			.createLogger(BaseAutoCompleteComboBoxModel.class);

	protected List<ItemWrapper<T>> data;

	private ItemWrapper<T> selected;

	private boolean processingEnabled;

	private AutoCompleteComboBoxDocument document;

	private ItemWrapper<T> lastSelected;

	private boolean updatePopup;

	void setDocument(AutoCompleteComboBoxDocument val) {
		document = val;
	}

	/**
	 * 
	 * @return document that is installed at decorated JComboBox
	 */
	public AutoCompleteComboBoxDocument getDocument() {
		return document;
	}

	boolean setPattern(String pattern) {
		if (pattern != null) {
			pattern = pattern.toLowerCase();
		}
		logger.fine(String.format("getData(%s)", pattern));
		boolean success = preprocessFiltering(pattern);
		if (!success) {
			return false;
		}
		updateFilteredData();
		data = getFilteredData();
		fireContentsChanged(this, -1, -1);
		setSelectedItem(data.get(0));
		return true;
	}

	void enableProcessing() {
		if (!processingEnabled) {
			processingEnabled = true;
			fireContentsChanged(this, -1, -1);
		}
	}

	/**
	 * overriding parent method to make it visible in
	 * AutoCompleteComboBoxDecorator
	 */
	@Override
	protected void fireContentsChanged(Object source, int index0, int index1) {
		super.fireContentsChanged(source, index0, index1);
	}

	@Override
	public ItemWrapper<T> getSelectedItem() {
		return selected;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setSelectedItem(Object anObject) {
		logger.fine("selecting " + anObject);
		if (processingEnabled && !isValid((ItemWrapper<T>) anObject)) {
			logger.fine("item is not set - illegal argument");
			return;
		}
		if ((selected != null && !selected.equals(anObject))
				|| selected == null && anObject != null) {
			selected = (ItemWrapper<T>) anObject;
			fireContentsChanged(this, -1, -1);
			logger.fine("selected item changed " + anObject);
		}
		logger.fine("selected " + anObject);
	}

	@Override
	public int getSize() {
		if (!processingEnabled) {
			return 1;
		}
		if (data == null) {
			logger.fine("loadData()");
			loadData();
			data = getFilteredData();
		}
		return data.size();
	}

	@Override
	public Object getElementAt(int index) {
		if (!processingEnabled) {
			return selected;
		}
		if (data == null) {
			logger.fine("loadData()");
			loadData();
			data = getFilteredData();
		}
		return data.get(index);
	}

	boolean isProcessingEnabled() {
		return processingEnabled;
	}

	/**
	 * 
	 * @return element that was selected last time, that is before time when
	 *         popup was visible. This method is used in
	 *         ShortenedListComboBoxModel
	 */
	protected ItemWrapper<T> getLastSelected() {
		return lastSelected;
	}

	void setLastSelected(ItemWrapper<T> value) {
		lastSelected = value;
	}

	protected boolean isUpdatePopup() {
		return updatePopup;
	}

	protected void setUpdatePopup(boolean value) {
		updatePopup = value;
	}

	/**
	 * in this method model must try to filter available data with given pattern
	 * and if the result is not empty - store the result in temporary list
	 * 
	 * @param pattern
	 *            text that is used for filtering
	 * @return true if the result of filtering is not empty, false otherwise
	 */
	protected abstract boolean preprocessFiltering(String pattern);

	/**
	 * this function must copy results of preprocessing to filtered data
	 */
	protected abstract void updateFilteredData();

	/**
	 * @return list of filtered items
	 */
	protected abstract List<ItemWrapper<T>> getFilteredData();

	/**
	 * this function is called when list of items are queried for example when
	 * user press arrow button at combobox or begins to type
	 */
	protected abstract void loadData();

	/**
	 * this function is called when popup becomes invisible and next time popup
	 * opened the full list of items are to be displayed
	 */
	protected abstract void resetFilteredData();

	/**
	 * this function performes additional check whether item in selectItem()
	 * function is a valid object
	 * 
	 * @param anObject
	 *            object to be checked
	 * @return true if anObject is valid
	 */
	protected abstract boolean isValid(ItemWrapper<T> anObject);

}
